#!/usr/bin/env python
# -*- coding: utf-8 -*-

# ----------------------------------------------------------------------
# Copyright (c) 2014 Pablo Caro. All Rights Reserved.
# Pablo Caro <me@pcaro.es> - https://pcaro.es/
# hmac.py
# port to micropython by rileyge @ 2022-04-14
# for more information refer to: https://gitee.com/rlge/hmac_sha1_micropython
# ----------------------------------------------------------------------
import ubinascii

class SHA1:
    def __init__(self):
        self.__H = [
            0x67452301,
            0xEFCDAB89,
            0x98BADCFE,
            0x10325476,
            0xC3D2E1F0
            ]

    def __str__(self):
        return ''.join('{:0>8}'.format(hex(h)[2:]) for h in self.__H)

    # Private static methods used for internal operations.
    @staticmethod
    def __ROTL(n, x, w=32):
        return ((x << n) | (x >> w - n))

    @staticmethod
    def __padding(stream):
        l = len(stream)  # Bytes
#         hl = [int((hex(l*8)[2:]).rjust(16, '0')[i:i+2], 16)
#               for i in range(0, 16, 2)]
        hl = [int('{:0>16}'.format(hex(l*8)[2:])[i:i+2], 16)
              for i in range(0, 16, 2)]

        l0 = (56 - l) % 64
        if not l0:
            l0 = 64

        if isinstance(stream, str):
            stream += chr(0b10000000)
            stream += chr(0)*(l0-1)
            for a in hl:
                stream += chr(a)
        elif isinstance(stream, bytes):
            stream += bytes([0b10000000])
            stream += bytes(l0-1)
            stream += bytes(hl)

        return stream

    @staticmethod
    def __prepare(stream):
        M = []
        n_blocks = len(stream) // 64

        stream = bytearray(stream)

        for i in range(n_blocks):  # 64 Bytes per Block
            m = []

            for j in range(16):  # 16 Words per Block
                n = 0
                for k in range(4):  # 4 Bytes per Word
                    n <<= 8
                    n += stream[i*64 + j*4 + k]

                m.append(n)

            M.append(m[:])

        return M

    @staticmethod
    def __debug_print(t, a, b, c, d, e):
        print('t = {0} : \t'.format(t),
              '{:0>8}'.format(hex(a)[2:]),
              '{:0>8}'.format(hex(b)[2:]),
              '{:0>8}'.format(hex(c)[2:]),
              '{:0>8}'.format(hex(d)[2:]),
              '{:0>8}'.format(hex(e)[2:])
              )

    # Private instance methods used for internal operations.
    def __process_block(self, block):
        MASK = 2**32-1

        W = block[:]
        for t in range(16, 80):
            W.append(SHA1.__ROTL(1, (W[t-3] ^ W[t-8] ^ W[t-14] ^ W[t-16]))
                     & MASK)

        a, b, c, d, e = self.__H[:]

        for t in range(80):
            if t <= 19:
                K = 0x5a827999
                f = (b & c) ^ (~b & d)
            elif t <= 39:
                K = 0x6ed9eba1
                f = b ^ c ^ d
            elif t <= 59:
                K = 0x8f1bbcdc
                f = (b & c) ^ (b & d) ^ (c & d)
            else:
                K = 0xca62c1d6
                f = b ^ c ^ d

            T = ((SHA1.__ROTL(5, a) + f + e + K + W[t]) & MASK)
            e = d
            d = c
            c = SHA1.__ROTL(30, b) & MASK
            b = a
            a = T

            #SHA1.debug_print(t, a,b,c,d,e)

        self.__H[0] = (a + self.__H[0]) & MASK
        self.__H[1] = (b + self.__H[1]) & MASK
        self.__H[2] = (c + self.__H[2]) & MASK
        self.__H[3] = (d + self.__H[3]) & MASK
        self.__H[4] = (e + self.__H[4]) & MASK

    # Public methods for class use.
    def update(self, stream):
        stream = SHA1.__padding(stream)
        stream = SHA1.__prepare(stream)

        for block in stream:
            self.__process_block(block)

    def digest(self):
        s = self.hexdigest()
        return ubinascii.unhexlify(s)

    def hexdigest(self):
        s = ''
        for h in self.__H:
            s += '{:0>8}'.format(hex(h)[2:])
        return s

def sha1(content):
    h = SHA1()
    h.update(content)
    return h

# HMAC implementation, as hashlib/hmac wouldn't fit
# From https://en.wikipedia.org/wiki/Hash-based_message_authentication_code
def hmac_sha1(key, msg):
    SHA1_BLOCK_SIZE = 64
    KEY_BLOCK = key + (b'\0' * (SHA1_BLOCK_SIZE - len(key)))
    KEY_INNER = bytes((x ^ 0x36) for x in KEY_BLOCK)
    KEY_OUTER = bytes((x ^ 0x5C) for x in KEY_BLOCK)
    inner_message = KEY_INNER + msg
    outer_message = KEY_OUTER + sha1(inner_message).digest()
    return sha1(outer_message)


# hex_sha = sha1(b'my love').hexdigest()
# bin_sha = sha1(b'my love').digest()
# print(hex_sha, bin_sha)
# 
# hex_hmac_sha = hmac_sha1(b'my love', b'your love').hexdigest()
# bin_hmac_sha = hmac_sha1(b'my love', b'your love').digest()
# print(hex_hmac_sha, bin_hmac_sha)
    